import time
import os
import json
import tkinter as tk
from tkinter import ttk, scrolledtext, filedialog, messagebox
import pandas as pd
from selenium import webdriver
from selenium.webdriver.common.by import By
from webdriver_manager.core.driver_cache import DriverCacheManager
from webdriver_manager.firefox import GeckoDriverManager
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.microsoft import EdgeChromiumDriverManager
from selenium.webdriver.firefox.options import Options as firefoxOptions
from selenium.webdriver.firefox.service import Service as firefoxService
from selenium.webdriver.edge.options import Options as edgeOptions
from selenium.webdriver.edge.service import Service as edgeService
from selenium.webdriver.chrome.options import Options as chromeOptions
from selenium.webdriver.chrome.service import Service as chromeService
from selenium.common.exceptions import WebDriverException

from datetime import datetime
from threading import Thread
from queue import Queue
import traceback
import random


class CompactApp:
    def __init__(self, root):
        self.driver = None
        self.in_file = None
        self.config = None
        self.out_dir = 'D:\\py_down'
        self.root = root
        self.queue = Queue()
        self.setup_ui()
        self.check_queue()  # 启动队列检查

    def setup_ui(self):
        root.title("任务执行工具")
        # 例子：长x宽+距屏左侧+距屏顶
        root.geometry('500x300+600+200')
        root.resizable(True, True)
        # 设置透明度
        root.attributes('-alpha', 1)
        # 设置窗口置顶
        root.attributes('-topmost', True)

        # 主框架
        main_frame = ttk.Frame(root, padding=(10, 5))
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 设置网格布局
        main_frame.columnconfigure(0, weight=0, minsize=80)  # 标签列
        main_frame.columnconfigure(1, weight=1)  # 输入列
        main_frame.columnconfigure(2, weight=0, minsize=100)  # 按钮列

        # 功能选择
        ttk.Label(main_frame, text="功能:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5), pady=2)
        self.function_var = tk.StringVar()
        # 第一行
        row1 = ttk.Frame(main_frame)
        row1.grid(row=0, column=1, sticky=tk.EW, padx=0, pady=2)
        self.functools_link = {'批量下载': 'down.json'}
        functions = ["批量下载"]
        self.function_cb = ttk.Combobox(row1, textvariable=self.function_var, values=functions, width=12)
        self.function_cb.grid(row=0, column=1, sticky=tk.W, padx=5, pady=2)
        self.function_cb.current(0)
        # 浏览器
        ttk.Label(row1, text="浏览器:").grid(row=0, column=2, sticky=tk.W, padx=5, pady=2)
        self.browser_var = tk.StringVar()
        browser = ["火狐", "谷歌", "Edge"]
        self.function_br = ttk.Combobox(row1, textvariable=self.browser_var, values=browser, width=12)
        self.function_br.grid(row=0, column=3, sticky=tk.W, padx=(2, 0), pady=2)
        self.function_br.current(0)

        # 域名输入
        ttk.Label(main_frame, text="域名:").grid(row=1, column=0, sticky=tk.W, padx=(0, 5), pady=2)
        self.domain = ttk.Entry(main_frame, width=25)
        self.domain.insert(0, 'https://testbm.htyunjiaohui.com')
        self.domain.grid(row=1, column=1, columnspan=2, sticky=tk.EW, padx=5, pady=2)

        # 账号输入
        ttk.Label(main_frame, text="账号:").grid(row=2, column=0, sticky=tk.W, padx=(0, 5), pady=2)
        self.username = ttk.Entry(main_frame, width=25)
        self.username.grid(row=2, column=1, columnspan=2, sticky=tk.EW, padx=5, pady=2)
        self.username.insert(0, 'lwc')

        # 密码输入
        ttk.Label(main_frame, text="密码:").grid(row=3, column=0, sticky=tk.W, padx=(0, 5), pady=2)
        self.password = ttk.Entry(main_frame, width=25, show="*")
        self.password.grid(row=3, column=1, columnspan=2, sticky=tk.EW, padx=5, pady=2)
        self.password.insert(0, 'aaaaa110')

        # 文件操作按钮
        ttk.Button(main_frame, text="选择文件", command=self.select_file, width=10).grid(
            row=5, column=0, columnspan=1, padx=(0, 5), pady=2, sticky=tk.W)
        ttk.Button(main_frame, text="输出目录", command=self.select_output, width=10).grid(
            row=5, column=1, columnspan=2, padx=5, pady=2, sticky=tk.W)
        # 执行按钮
        self.executeBtn = ttk.Button(main_frame, text="确认执行", command=self.execute, width=12)
        self.executeBtn.grid(row=5, column=2, columnspan=3, pady=8)

        # 第七行：日志区域
        log_frame = ttk.Frame(main_frame)
        log_frame.grid(row=6, column=0, columnspan=3, sticky=tk.NSEW, pady=(5, 0))

        # 日志文本框（带滚动条）
        self.log_area = tk.Listbox(
            log_frame,
            height=1,  # 默认只显示一行
            font=('TkDefaultFont', 9),
            relief=tk.FLAT
        )
        self.log_area.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # 滚动条
        scrollbar = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, command=self.log_area.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.log_area.config(yscrollcommand=scrollbar.set)

        # 折叠/展开按钮
        self.expand_button = ttk.Button(
            main_frame,
            text="▼ 展开日志",
            width=12,
            command=self.toggle_log
        )
        self.expand_button.grid(row=7, column=2, sticky=tk.E, padx=5, pady=(2, 5))
        self.log_expanded = False

        # 配置网格权重
        main_frame.rowconfigure(6, weight=1)

    def toggle_log(self):
        """切换日志区域大小"""
        self.log_expanded = not self.log_expanded
        self.log_area.config(height=10 if self.log_expanded else 1)
        self.expand_button.config(text="▲ 收起日志" if self.log_expanded else "▼ 展开日志")

    def select_file(self):
        file_path = filedialog.askopenfilename(filetypes=[('excel', '*.xlsx'), ('excel', '*.xls')], title='源数据')
        if file_path:
            self.add_log(f"选择文件: {file_path}")
            self.in_file = file_path

    def select_output(self):
        dir_path = filedialog.askdirectory()
        if dir_path:
            self.add_log(f"输出目录: {dir_path}")
            self.out_dir = dir_path

    def execute(self):
        self.log_area.delete(0, tk.END)
        self.root.title("任务执行工具")
        self.executeBtn.config(text="执行中...", state="disabled")
        self.add_log("开始执行任务...")
        # 模拟执行过程
        self.add_log(
            f"已选功能：{self.function_var.get()},浏览器：{self.browser_var.get()},域名：{self.domain.get()},用户：{self.username.get()},密码：{self.password.get()}")
        self.add_log(f"输出目录：{self.out_dir}")
        self.add_log(f"所选文件：{self.in_file}")
        if self.in_file == None:
            messagebox.showerror('提示', '未选择文件')
            self.add_log("未选择文件")
            self.errorEnd()
            return

        # 读取功能配置
        self.config = self.handelJson()
        thread = Thread(target=self.background_task, daemon=True)
        thread.start()

    def background_task(self):
        self.initDriver()
        self.handelReadExcel()
        self.end()

    def check_queue(self):
        while not self.queue.empty():
            msg = self.queue.get()
            self.root.title(f"任务执行工具，{msg}")
            print(f"任务执行工具,{msg}")
        self.root.after(100, self.check_queue)  # 每100ms检查队列

    def end(self):
        self.add_log("任务完成!")
        self.executeBtn.config(text="确认执行", state="normal")
        os.startfile(self.out_dir)
        self.driver.close()
        self.driver.quit()

    def errorEnd(self):
        self.executeBtn.config(text="确认执行", state="normal")
        self.driver.close()
        self.driver.quit()

    def initDriver(self):
        root_path = os.getcwd()  # 得到绝对路径
        try:
            cache_manager = DriverCacheManager(root_dir=root_path)
            if self.browser_var.get() == '火狐':
                # 火狐配置下载参数
                self.add_log(f"开始下载火狐浏览器驱动")
                path_all = GeckoDriverManager(cache_manager=cache_manager).install()
                self.add_log(f"路径：{path_all}")
                # path_all = os.path.join(root_path) + '\\geckodriver.exe'
                # print(path_all)
                # 驱动下载地址：https://github.com/mozilla/geckodriver/releases
                options = firefoxOptions()
                options.set_preference("browser.download.folderList", 2)
                options.set_preference("browser.download.dir", self.out_dir)
                options.set_preference("browser.helperApps.neverAsk.saveToDisk", "application/pdf")
                # options.add_argument("--headless")  # 启用无头模式:ml-citation{ref="1,3" data="citationList"}
                options.add_argument("--width=1920")  # 设置宽度
                options.add_argument("--height=1080")  # 设置高度
                if os.path.isfile(path_all):
                    self.driver = webdriver.Firefox(service=firefoxService(executable_path=path_all), options=options)
                else:
                    self.driver = webdriver.Firefox(options=options)
            if self.browser_var.get() == 'Edge':
                # 火狐配置下载参数
                self.add_log(f"开始下载Edge浏览器驱动")
                path_all = EdgeChromiumDriverManager(cache_manager=cache_manager).install()
                self.add_log(f"路径：{path_all}")
                # path_all = os.path.join(root_path) + '\\geckodriver.exe'
                # print(path_all)
                # 驱动下载地址：https://github.com/mozilla/geckodriver/releases
                options = edgeOptions()
                # 设置下载参数
                options.add_experimental_option("prefs", {
                    "download.default_directory": self.out_dir,
                    "download.prompt_for_download": False,
                    "safebrowsing.enabled": True
                })
                # 设置PDF自动下载
                options.add_experimental_option("excludeSwitches", ["enable-logging"])
                # 设置窗口尺寸
                options.add_argument("--window-size=1920,1080")
                # options.add_argument("--headless")  # 无头模式
                if os.path.isfile(path_all):
                    self.driver = webdriver.Edge(service=edgeService(executable_path=path_all), options=options)
                else:
                    self.driver = webdriver.Edge(options=options)
            if self.browser_var.get() == '谷歌':
                # Google
                self.add_log(f"开始下载谷歌浏览器驱动")
                path_all = ChromeDriverManager(cache_manager=cache_manager).install()
                self.add_log(f"路径：{path_all}")
                # path_all = os.path.join(root_path) + '\\chromedriver.exe'
                options = chromeOptions()
                options.add_experimental_option("prefs", {
                    "download.default_directory": self.out_dir,
                    "download.prompt_for_download": False,
                    "download.directory_upgrade": True,
                    "plugins.always_open_pdf_externally": True  # PDF自动下载
                })
                # 无头模式和窗口设置
                # options.add_argument("--headless")  # 无头模式
                options.add_argument("--window-size=1920,1080")  # 窗口尺寸
                # 其他常用参数
                options.add_argument("--disable-gpu")  # 禁用GPU加速
                options.add_argument("--no-sandbox")  # 沙盒模式
                options.add_argument("--disable-dev-shm-usage")  # 共享内存
                if os.path.isfile(path_all):
                    self.driver = webdriver.Chrome(service=chromeService(executable_path=path_all), options=options)
                else:
                    self.driver = webdriver.Chrome(options=options)
        except WebDriverException as e:
            self.add_log(f"启动浏览器失败: {str(e)}")
            self.errorEnd()
        self.driver.get(self.domain.get())

        if 'implicitly_wait' in self.config['login']:
            self.driver.implicitly_wait(self.config['login']['implicitly_wait'])
        # 登录相关操作
        # 元素定位
        el1 = self.driver.find_element(By.XPATH, self.config['login']['userNameEl'])
        # 设置值
        el1.clear()
        el1.send_keys(self.username.get())
        # 文本值定位 精确定位
        # el2 = driver.find_element(By.LINK_TEXT,'欢迎来到云交会管理后台！')
        # print(el2.get_attribute('class'))
        # 文本值定位 模糊定位
        el3 = self.driver.find_element(By.XPATH, self.config['login']['passWordEl'])
        # 清空内容
        el3.clear()
        el3.send_keys(self.password.get())
        print(el3.get_attribute('value'))
        self.driver.find_element(By.XPATH, self.config['login']['loginBtnEl']).click()
        # 强制等待
        # time.sleep(2)
        if 'wait_time' in self.config['login']:
            time.sleep(self.config['login']["wait_time"])  # 等待下载完成

    # 加载并解析json
    def handelJson(self):
        config = None
        try:
            with open(self.functools_link[self.function_var.get()], encoding='utf-8') as f:
                config = json.load(f)
                self.add_log("成功加载配置")
                print(f"成功加载配置：{config}")
                # for item in config['steps']:
                #     self.add_log(f"{item['name']}")
        except FileNotFoundError:
            self.add_log("错误：配置文件不存在")
            self.errorEnd()
        except json.JSONDecodeError:
            self.add_log("错误：JSON格式无效")
            self.errorEnd()

        return config

    def handelReadExcel(self):
        if not self.in_file:
            messagebox.showerror('提示', '未选择文件')
            self.add_log("未选择文件")
            self.errorEnd()
            return
        try:
            # 读取Excel数据
            df = pd.read_excel(self.in_file)
            # 遍历数据
            self.add_log(f"共{len(df)}行数据：")
            for index, row in df.iterrows():
                self.queue.put(f"进度: {int((index + 1) / len(df) * 100)}%")  # 通过队列传递数据
                self.add_log(f"第{index + 1}行：{row.to_dict()}")
                self.execute_flow(row.to_dict())

        except Exception as e:
            self.add_log(f"读取失败：{str(e)}")
            self.errorEnd()

    def execute_flow(self, row):
        for step in self.config["steps"]:
            self._execute_step(step, row)

    def _execute_step(self, step, row):
        # self.add_log(f"开始执行操作：{str(step['name'])}")
        action = step["action"]
        if 'implicitly_wait' in step:
            self.driver.implicitly_wait(step['implicitly_wait'])
        try:
            if action == 'click':
                if 'implicitly_wait' in step:
                    self.driver.implicitly_wait(step['implicitly_wait'])
                if 'xpathEl' not in step:
                    raise Exception('定位元素不存在')
                self.driver.find_element(By.XPATH, step['xpathEl']).click()
                # self.add_log(f"点的元素：{step['xpathEl']}")
            elif action == 'input':
                if len(step['inputs']) > 0:
                    for input in step['inputs']:
                        tmpValue = input['value']
                        # 1值2取excel当前行的列值
                        if 'value_type' not in input:
                            input['value_type'] = 1
                        if input['value_type'] == 2:
                            tmpValue = row[input['value']]
                        el = self.driver.find_element(By.XPATH, input['xpathEl'])
                        el.clear()
                        el.send_keys(tmpValue)
                if 'xpathEl' in step:
                    self.driver.find_element(By.XPATH, step['xpathEl']).click()
                    # self.add_log(f"点的元素：{step['xpathEl']}")
                if 'wait_time' in step:
                    time.sleep(step["wait_time"])  # 等待下载完成
            elif action == "download":
                self.driver.find_element(By.XPATH, step['xpathEl']).click()
                # self.add_log(f"点的元素：{step['xpathEl']}")
                if 'wait_time' in step:
                    time.sleep(float(step["wait_time"]))  # 等待下载完成
                self.rename_latest_file(datetime.now().strftime("%Y%m%d-") + str(random.randint(11111, 99999)) + str(
                    row[step['file_name']]))
            else:
                self.add_log(f"配置错误{action},{step['name']}")
        except Exception as e:
            traceback.print_exc()
            self.add_log(f"执行失败了：{e}")
            self.end()

    def rename_latest_file(self, new_name):
        # 获取目录下所有文件并按修改时间排序
        files = [os.path.join(self.out_dir, f) for f in os.listdir(self.out_dir)]
        files = [f for f in files if os.path.isfile(f)]
        files.sort(key=lambda x: os.path.getmtime(x), reverse=True)

        if files:
            latest_file = files[0]
            file_ext = os.path.splitext(latest_file)[1]
            new_path = os.path.join(self.out_dir, f"{new_name}{file_ext}")
            os.rename(latest_file, new_path)
            return new_path
        return None

    def add_log(self, message):
        """添加日志消息"""
        print(message)
        self.log_area.insert(tk.END, message)
        self.log_area.see(tk.END)  # 自动滚动到底部
        # 保持只显示一行（除非展开）
        # if not self.log_expanded and self.log_area.size() > 1:
        #     self.log_area.delete(0, self.log_area.size() - 2)


if __name__ == "__main__":
    root = tk.Tk()
    app = CompactApp(root)
    root.mainloop()
